﻿using System;
using System.Collections.Generic;
using System.Linq;
using TaleWorlds.CampaignSystem;
using TaleWorlds.CampaignSystem.Actions;
using TaleWorlds.CampaignSystem.Party;
using TaleWorlds.CampaignSystem.Party.PartyComponents;
using TaleWorlds.CampaignSystem.Settlements;
using TaleWorlds.Library;
using TaleWorlds.Localization;
using MarriageFertility.Texts;
using MarriageFertility.Mcm;

namespace MarriageFertility.Behaviors
{
    public class PartyLeaveRequestBehavior :
        CampaignBehaviorBase
    {
        private Dictionary<string, CampaignTime> _npcTownStayStartTimes = new();
        private Dictionary<string, CampaignTime> _pendingRequests = new();

        public override void RegisterEvents()
        {
            CampaignEvents.DailyTickEvent.AddNonSerializedListener(this, OnDailyTick);
            CampaignEvents.SettlementEntered.AddNonSerializedListener(this, OnSettlementEntered);
            CampaignEvents.OnSettlementLeftEvent.AddNonSerializedListener(this, OnSettlementLeft);
            CampaignEvents.HeroKilledEvent.AddNonSerializedListener(this, OnHeroKilled);
        }

        private void OnDailyTick()
        {
            var playerParty = PartyBase.MainParty?.MobileParty;
            if (!MCMSettings.Instance.EnablePartyLeave || playerParty == null) return;
            Settlement currentTown = playerParty.CurrentSettlement;
            if (currentTown == null || !currentTown.IsTown) return;

            foreach (Hero hero in playerParty.MemberRoster.GetTroopRoster()
                         .Where(t => t.Character.IsHero && t.Character.HeroObject != Hero.MainHero &&
                                     t.Character.HeroObject.IsAlive)
                         .Select(t => t.Character.HeroObject))
            {
                if (!_npcTownStayStartTimes.ContainsKey(hero.StringId))
                {
                    _npcTownStayStartTimes[hero.StringId] = CampaignTime.Now;
                }

                double daysInTown = CampaignTime.Now.ToDays - _npcTownStayStartTimes[hero.StringId].ToDays;

                if (_pendingRequests.ContainsKey(hero.StringId))
                {
                    double daysSinceRequest = CampaignTime.Now.ToDays - _pendingRequests[hero.StringId].ToDays;
                    if (daysSinceRequest >= MCMSettings.Instance.RequestTimeoutDays - 1)
                    {
                        _npcTownStayStartTimes[hero.StringId] = CampaignTime.Now;
                        _pendingRequests.Remove(hero.StringId);
                        DisplayMessage(hero, PartyText.WithdrawnRequestMessage);
                    }

                    continue;
                }

                if (daysInTown >= MCMSettings.Instance.DaysInTownThreshold &&
                    !_pendingRequests.ContainsKey(hero.StringId))
                {
                    ShowLeaveRequestDialog(hero, currentTown);
                }
            }
        }

        private void OnSettlementEntered(MobileParty party, Settlement settlement, Hero hero)
        {
            if (party == null || party.LeaderHero != Hero.MainHero || settlement == null || !settlement.IsTown)
            {
                return;
            }

            if (party.MemberRoster.GetTroopRoster().Any(t =>
                    t.Character.IsHero && t.Character.HeroObject == hero && hero.IsAlive && hero != Hero.MainHero))
            {
                if (!_npcTownStayStartTimes.ContainsKey(hero.StringId))
                {
                    _npcTownStayStartTimes[hero.StringId] = CampaignTime.Now;
                }
            }
        }

        private void ShowLeaveRequestDialog(Hero hero, Settlement town)
        {
            TextObject title = PartyText.PartyLeaveRequestTitle;
            TextObject message = PartyText.PartyLeaveRequestMessage;
            message = message
                .SetTextVariable("days", MCMSettings.Instance.DaysInTownThreshold.ToString("F0"))
                .SetTextVariable("hero", hero.Name)
                .SetTextVariable("town", town.Name.ToString());

            title = title.SetTextVariable("hero", hero.Name);

            InformationManager.ShowInquiry(
                new InquiryData(
                    title.ToString(),
                    message.ToString(),
                    true,
                    true,
                    PartyText.PartyLeaveRequestApprove,
                    PartyText.PartyLeaveRequestDeny,
                    () => OnApproveRequest(hero, town),
                    () => OnDenyRequest(hero)
                ),
                true
            );

            _pendingRequests[hero.StringId] = CampaignTime.Now;
        }

        private void OnApproveRequest(Hero hero, Settlement town)
        {
            if (hero.Age < 18)
            {
                _pendingRequests.Remove(hero.StringId);
                DisplayMessage(hero, PartyText.TooYoungMessage);
                return;
            }

            MobileParty oldParty = hero.PartyBelongedTo;
            oldParty?.MemberRoster.RemoveTroop(hero.CharacterObject);

            MobileParty newParty = LordPartyComponent.CreateLordParty(
                hero.StringId + "_party",
                hero,
                MobileParty.MainParty.Position2D,
                3f,
                town,
                hero
            );

            newParty.ChangePartyLeader(hero);
            newParty.SetCustomName(PartyText.PartyName.SetTextVariable("hero", hero.Name));

            int maxSize = newParty.LimitedPartySize;
            int currentSize = newParty.MemberRoster.TotalManCount;
            int spaceLeft = Math.Max(0, maxSize - currentSize);

            float eliteRatio = MCMSettings.Instance.EliteTroopRatio / 100;
            int eliteCount = (int)(spaceLeft * eliteRatio);
            int basicCount = spaceLeft - eliteCount;

            if (basicCount > 0)
                newParty.MemberRoster.AddToCounts(Clan.PlayerClan.Culture.BasicTroop, basicCount);
            if (eliteCount > 0)
                newParty.MemberRoster.AddToCounts(Clan.PlayerClan.Culture.EliteBasicTroop, eliteCount);

            int totalAdded = basicCount + eliteCount;
            int grainAmount = totalAdded * 10;
            newParty.ItemRoster.AddToCounts(DefaultItems.Grain, grainAmount);

            newParty.Party.SetVisualAsDirty();

            _npcTownStayStartTimes.Remove(hero.StringId);
            _pendingRequests.Remove(hero.StringId);
            DisplayMessage(hero, PartyText.FormedPartyMessage);
        }

        private void OnDenyRequest(Hero hero)
        {
            _npcTownStayStartTimes[hero.StringId] = CampaignTime.Now;
            _pendingRequests.Remove(hero.StringId);
            DisplayMessage(hero, PartyText.DeniedRequestMessage);
        }

        private void OnSettlementLeft(MobileParty party, Settlement settlement)
        {
            if (party == null || party.LeaderHero != Hero.MainHero) return;

            foreach (Hero hero in party.MemberRoster.GetTroopRoster()
                         .Where(t => t.Character.IsHero && t.Character.HeroObject != Hero.MainHero)
                         .Select(t => t.Character.HeroObject))
            {
                _npcTownStayStartTimes.Remove(hero.StringId);
                _pendingRequests.Remove(hero.StringId);
            }
        }

        private void OnHeroKilled(Hero victim, Hero killer, KillCharacterAction.KillCharacterActionDetail detail,
            bool showNotification)
        {
            _npcTownStayStartTimes.Remove(victim.StringId);
            _pendingRequests.Remove(victim.StringId);
        }

        private void DisplayMessage(Hero hero, string message)
        {
            TextObject text = PartyText.PartyLeaveNotification;
            text = text.SetTextVariable("hero", hero.Name);
            text = text.SetTextVariable("message", message);
            InformationManager.DisplayMessage(new InformationMessage(text.ToString(), Colors.Yellow));
        }

        public override void SyncData(IDataStore dataStore)
        {
            dataStore.SyncData("_npcTownStayStartTimes", ref _npcTownStayStartTimes);
            dataStore.SyncData("_pendingRequests", ref _pendingRequests);
        }
    }
}